try here

Release v0.3

Improved syncing + Node adapter

Build collaborative apps Build the next |
with synced SQLite

LiveStore is a next-generation state management framework based on reactive SQLite and git-inspired syncing (via event-sourcing).

Get started
"Events are the most accurate representation of state. LiveStore gets it right."
David Khourshid
David Khourshid

Works cross-platform

Framework integrations

Pluggable syncing provider

How it works

LiveStore is a fully-featured, client-centric data layer (replacing libraries like Redux, MobX, etc.) with a reactive embedded SQLite database powered by real-time sync (via event-sourcing).

Client A
renderArrow
UI
commit
events
Arrow
State
Events
reactive
query
Arrow
DB
materializeArrow
= Persisted in device storage

User Interface

LiveStore works with most UI frameworks (e.g. React, Solid, etc.) allowing you to reactively query the database and trigger changes by committing events.

Commit Events

Events are defined as part of your LiveStore schema. Events are committed by calling the commit method on the store.

schema.ts

import { Events, Schema } from '@livestore/livestore'

// Definition of a events
export const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String, completed: Schema.Boolean.pipe(Schema.optional) }),
  }),
  todoCompleted: Events.synced({
    name: 'v1.TodoCompleted',
    schema: Schema.Struct({ id: Schema.String }),
  }),
  todoUncompleted: Events.synced({
    name: 'v1.TodoUncompleted',
    schema: Schema.Struct({ id: Schema.String }),
  }),
  todoDeleted: Events.synced({
    name: 'v1.TodoDeleted',
    schema: Schema.Struct({ id: Schema.String, deletedAt: Schema.Date }),
  }),
}

todo.tsx

import { useStore } from '@livestore/react'
import { tables } from '../livestore/schema.js'

const Todo = ({ todo }: { todo: typeof tables.todos.Type }) => {
  const { store } = useStore()
  // Committed events update the database and are synced
  const complete = () => store.commit(events.todoCompleted(todo.id))

  return <div onClick={complete}>{todo.text}</div>
}

Events

When a event is committed, it's persisted in the eventlog, refreshes the database (via materializers) and synced to other clients (if sync is enabled).

Eventlog

eventNumbereventNameArgsClientIdSessionId
e0Root{}
e1v1.TodoCreated{"id":"djO3ELp02RxQgk4mRbJWF","text":"Learn about LiveStore","completed":true}GrETK0UW92hnRMIHGEgI3T_xJ8w6PFX6wzK5if0xJ8
e2v1.TodoCreated{"id":"t3vUQjBZX4hvzrjnsgJ-V","text":"Try LiveStore devtools"}GrETK0UW92hnRMIHGEgI3T_xJ8w6PFX6wzK5if0xJ8
e3v1.TodoCreated{"id":"MRPD4ICIBP58tpSZ89CB7","text":"Build a LiveStore app"}GrETK0UW92hnRMIHGEgI3T_xJ8w6PFX6wzK5if0xJ8

Materialize

Materializers are callback functions that map events to state changes via SQL statements. LiveStore comes with a built-in query builder.

schema.ts

import { State } from '@livestore/livestore'

// Materializers are used to map events to state
const materializers = State.SQLite.materializers(events, {
  'v1.TodoCreated': ({ id, text, completed }) => tables.todos.insert({ id, text, completed }),
  'v1.TodoCompleted': ({ id }) => tables.todos.update({ completed: true }).where({ id }),
  'v1.TodoUncompleted': ({ id }) => tables.todos.update({ completed: false }).where({ id }),
  'v1.TodoDeleted': ({ id, deletedAt }) => tables.todos.update({ deletedAt }).where({ id }),
})

Event

{
  eventNumber: 'e1',
  eventName: 'v1.TodoCreated',
  data: {
    id: 'djO3ELp02RxQgk4mRbJWF',
    text: 'Learn about LiveStore', 
    completed: true
  }
}

SQL

INSERT INTO todos (id, text, completed) 
VALUES (
  'djO3ELp02RxQgk4mRbJWF', 
  'Learn about LiveStore', 
  1
)

Database

LiveStore comes with an embedded reactive SQLite database which is automatically kept up to date via materializers and persisted to device storage.

schema.ts

import { Schema, State } from '@livestore/livestore'

// You can model your state as SQLite tables which you can reactively query
export const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text({ default: '' }),
      completed: State.SQLite.boolean({ default: false }),
      deletedAt: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
    },
  }),
}

Database

idtextcompleteddeletedAt
djO3ELp02RxQgk4mRbJWFLearn about LiveStoretruenull
t3vUQjBZX4hvzrjnsgJ-VTry LiveStore devtoolsfalsenull
MRPD4ICIBP58tpSZ89CB7Build a LiveStore appfalsenull

Reactive Query

LiveStore allows you to reactively query the database (via querybuilder, raw SQL, etc.). Query results are instant and don't require a loading state.

app.tsx

import { useQuery } from '@livestore/react'
import { queryDb } from '@livestore/livestore'
import { tables } from '../livestore/schema.js'

const todos$ = queryDb(
  (get) => tables.todos.where({ deletedAt: null })
)

export const Todos = () => {
  const todos = useQuery(todos$)
  // Reactively updates when the database changes. No loading state needed.

  return (
    <div>
      {todos.map((todo) => (
        <Todo todo={todo} />
      ))}
    </div>
  )
}

State (Query results)

Query results are returned as plain immutable JavaScript objects/arrays.

// Log to console to see the state
console.log(todos)

// [
//   {
//     id: 'djO3ELp02RxQgk4mRbJWF',
//     text: 'Learn about LiveStore',
//     completed: true
//   },
//   {
//     id: 't3vUQjBZX4hvzrjnsgJ-V',
//     text: 'Try LiveStore devtools',
//     completed: false
//   },
//   {
//     id: 'MRPD4ICIBP58tpSZ89CB7',
//     text: 'Build a LiveStore app',
//     completed: false
//   },
// ]

Render

Whenever the state changes, the component is re-rendered.

app.tsx

import { useQuery, useStore } from '@livestore/react'
import { tables } from '../livestore/schema.js'

const visibleTodos$ = queryDb(..) // see reactive query

export const Todos = () => {
  const todos = useQuery(visibleTodos$)

  return (
    <div>
      {todos.map((todo) => (
        <Todo todo={todo} />
      ))}
    </div>
  )
}

const Todo = ({ todo }: { todo: typeof tables.todos.Type }) => {
  const { store } = useStore()
  const complete = () => store.commit(events.todoCompleted(todo.id))

  return <div onClick={complete}>{todo.text}</div>
}

Let's look at a real example

The following is a simple TodoMVC app built with LiveStore showing how to model your events, state and reactively query the database.

See more examples on GitHub

Demos speak louder than words

LiveStore is designed for demanding & high-performance apps. Let's see it in action.

Fun fact: LiveStore was originally developed as a part of Overtone and later factored out.

Designed and optimized for demanding applications

LiveStore is based on years of research and was developed as the data foundation for uncompromising apps like Overtone.

Reactive & persisted SQLite

Reactive & persisted SQLite

LiveStore is based on SQLite enabling instant reactive queries while efficiently persisting data in the background.

Learn more
Real-time sync engine

Real-time sync engine

LiveStore includes a built-in sync engine based on event sourcing (similar to Git) allowing for complex syncing scenarios.

Learn more
Premium DX & devtools

Premium DX & devtools

For best-in-class developer experience, LiveStore offers first-class devtools similar to Chrome DevTools but for your data.

Learn more

High performance

LiveStore was designed for high-performance applications enabling developers to build complex apps running at 120 FPS.

Powerful type-safe schema

LiveStore offers a powerful type-safe schema API allowing for ergonomic data modeling and evolution without database migrations.

Local-first

LiveStore allows you to build local-first/offline-first apps by taking care of the hardest part: data management.

What LiveStore does vs. what not

LiveStore was designed to be a principled and flexible data layer. It's design decisions might make it unsuitable for some use cases. Learn more about when to use LiveStore.

What LiveStore does

  • Provide a powerful data foundation for your app.
  • Reactive query layer with full SQLite support.
  • Adapters for most platforms (web, mobile, server/edge, desktop).
  • Flexible data modeling and schema management.
  • Support true offline-first workflows.
  • Custom merge conflict resolution.
  • Sync with a supported provider or roll your own.
  • Helps avoid data vendor lock-in.

What LiveStore doesn't do

  • Not a batteries-included framework (no auth, file upload, etc).
  • Not a good fit for some use cases.
  • Doesn't sync with your existing database.
  • Doesn't provide a hosted service.
  • Doesn't scale for unbounded amounts of data.
  • Doesn't support peer-to-peer/decentralized syncing.
  • Sell your data.

What others are saying

Additional resources

Check out the following resources to learn more about LiveStore.

Conference talk

You can learn more about LiveStore in some of our past conference talks.

Read more

Office hours

Watch some of the past LiveStore office hours recordings and join the next one.

Read more

Riffle essay

In the Riffle essay (+ PhD thesis by Geoffrey Litt), we explored the idea of reactive SQLite as a modern state management system.

Read more

The story behind LiveStore

LiveStore was designed and developed as foundation for Overtone, a next-gen music app. To achieve the high-performance requirements of the app, we needed a state management framework that is able to handle the complex data scenarios of the app which started the Riffle research project and later became LiveStore.

LiveStore
Creator of
Overtone Prisma

Get started

Give LiveStore a try. Start with an existing example or add it to your own project.

Get started

Sponsor the project

Become a sponsor and get access to...

  • LiveStore devtools
  • Discord channel
  • Community
LiveStore BETA

Made with care by Overengineering Studio & contributors
© 2025